// gcc main.c -Wall -Wextra -g -std=c11 -o mac

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <stdint.h>

#define INSTR_LEN 2
#define MEMORY_SIZE 2000
#define sp (registers[SP])
#define ip (registers[PC])

#define PACK(a, b) (((uint16_t)(a) << 16) + (uint16_t)(b))

typedef enum
{
    MOV,
    MOVI,
    LOAD,
    SAVE,
    ADD,
    NOT,
    AND,
    OR,
    JMP,
    JZ,
    PUSH,
    POP,
    PUT,
    GET,
} InstructionSet;

typedef enum
{
    A,
    B,
    C,
    D,
    E,
    F,
    PC,
    SP,
    NUM_OF_REGISTERS
} Registers;

typedef struct
{
    int instr;
    int data1;
    // int data2;
} sinstr;

// int program[] = {
//     PACK(MOVI, A), 64,
//     PACK(MOVI, B), 1,
//     PACK(ADD, A), B,
//     PACK(PUT, A),
//     // jmp forever
//     PACK(JMP, 0)};
int *memory;
// a = pc;
// copy_all();
// copy_all()
// {
//     c = -len;
//     b = a + len;
//     while (c != 0)
//     {
//         *b = *a;
//         a++;
//         b++;
//         c++;
//     }
// }
int program[] = {
    PACK(MOV, A), PC,
    PACK(MOVI, C), -25,
    PACK(MOVI, D), 25,
    PACK(MOV, B), A,
    PACK(ADD, B), D,
    // LOOP:
    PACK(JZ, C), 15,
    PACK(LOAD, E), A,
    PACK(SAVE, B), E,
    PACK(MOVI, F), 1,
    PACK(ADD, A), F,
    PACK(ADD, B), F,
    PACK(ADD, C), F,
    PACK(JMP, -14)};

int stack[256];
int registers[NUM_OF_REGISTERS];
bool verbose = false;

void pins(int instr)
{
    char *m[] = {
        "MOV", "MOVI", "LOAD", "SAVE",
        "ADD", "NOT", "AND", "OR", "JMP", "JZ",
        "PUSH", "POP", "PUT", "GET"};
    printf("%s ", m[instr]);
}
void vins(int instr)
{
    if (verbose)
        pins(instr);
}
void preg(int a, int b)
{
    char *m[] = {"A", "B", "C", "D", "E", "F", "PC", "SP"};
    if (b < 0)
    {
        printf("%s", m[a]);
    }
    else
    {
        printf("%s, %s", m[a], m[b]);
    }
}
void vreg(int a, int b)
{
    if (verbose)
        preg(a, b);
}
void vregs()
{
    if (verbose)
    {
        for (int i = 0; i < NUM_OF_REGISTERS; i++)
        {
            printf("\t");
            preg(i, -1);
            printf(": %d", registers[i]);
        }
    }
}

sinstr fetch()
{
    if (ip >= MEMORY_SIZE - 1)
    {
        perror("memory size full");
        exit(1);
    }
    int a = memory[ip];
    sinstr s;
    s.instr = (uint32_t)a >> 16;
    s.data1 = (int16_t)((uint32_t)a & 0xffff);
    return s;
}
int fetchdata(int index)
{
    return memory[ip + index];
}

void eval(sinstr instr)
{
    vins(instr.instr);
    int a = instr.data1, b;
    // need to check every registers index, but too tired
    switch (instr.instr)
    {
    case MOV:
        b = memory[ip + 1];
        vreg(a, b);
        registers[a] = registers[b];
        break;
    case MOVI:
        b = memory[ip + 1];
        vreg(a, -1);
        if (verbose)
            printf(", %d", b);
        registers[a] = b;
        break;
    case LOAD:
        b = memory[ip + 1];
        vreg(a, b);
        if (registers[b] >= MEMORY_SIZE)
        {
            perror("virtual memory full");
            exit(1);
        }
        registers[a] = memory[registers[b]];
        break;
    case SAVE:
        b = memory[ip + 1];
        vreg(a, b);
        if (registers[a] >= MEMORY_SIZE)
        {
            perror("virtual memory full");
            exit(1);
        }
        memory[registers[a]] = registers[b];
        break;
    case ADD:
        b = memory[ip + 1];
        vreg(a, b);
        registers[a] += registers[b];
        break;
    case NOT:
        vreg(a, -1);
        registers[a] = ~registers[a];
        ip--;
        break;
    case AND:
        b = memory[ip + 1];
        vreg(a, b);
        registers[a] &= registers[b];
        break;
    case OR:
        b = memory[ip + 1];
        vreg(a, b);
        registers[a] |= registers[b];
        break;
    case JMP:
        if (verbose)
            printf("%4d", a);
        ip += a - INSTR_LEN;
        break;
    case JZ:
        b = memory[ip + 1];
        vreg(a, -1);
        if (verbose)
            printf(", %d", b);
        if (registers[a] == 0)
        {
            ip += b - INSTR_LEN;
        }
        break;
    case PUSH:
        vreg(a, -1);
        stack[++sp] = registers[a];
        ip--;
        break;
    case POP:
        vreg(a, -1);
        registers[a] = stack[sp--];
        ip--;
        break;
    case PUT:
        vreg(a, -1);
        fputc(registers[a], stdout);
        fflush(stdout);
        ip--;
        break;
    case GET:
        vreg(a, -1);
        registers[a] = fgetc(stdin);
        ip--;
        break;
    }
    if (verbose)
    {
        vregs();
        printf("\n");
    }
}

int main(int argc, char **argv)
{
    if (argc > 1 && strcmp(argv[1], "-v") == 0)
    {
        verbose = true;
    }

    if (sizeof(int) != 4)
    {
        printf("sizeof int is not 4");
        exit(1);
    }

    // init
    sp = -1;
    ip = 0;
    memory = malloc(MEMORY_SIZE);
    if (memory == NULL)
    {
        perror("malloc");
        exit(1);
    }
    // load program
    memcpy(memory, program, sizeof(program));

    // run
    for (int i = 0; i < 20091; i++)
    {
        eval(fetch());
        ip += 2;
    }

    free(memory);
    return 0;
}